November 2009

Volume 24 Number 11

Cutting Edge - Conditional Rendering in ASP.NET AJAX 4.0

By Dino Esposito | November 2009

Client-side rendering is by far the most exciting, and long-awaited, new feature you’ll find in ASP.NET AJAX 4. Client-side rendering allows you to use HTML templates to define your desired layout and supplies a text-based syntax for placeholders of run-time data. It all looks like server-side data binding, except that it takes place within the client browser with external data downloaded via Web services.

Last month, I covered the basics of the new DataView client control and the binding techniques that will be most commonly used. In this article, I’ll go one step further and cover conditional template rendering.

Conditional Template Rendering 

In ASP.NET AJAX 4, an HTML template for data binding is a piece of markup that may contain ASP.NET markup, HTML literals and some placeholders for run-time data. The rendering algorithm is fairly simple: bound to such templates, the DataView control fetches some data and uses that to fill up the template. The resulting markup, with actual data substituted for placeholders, is then displayed in lieu of the original HTML template.

There are a couple of ways in which you can create an instance of the DataView control: declaratively or programmatically. However, the algorithm used to generate the markup remains the same. This is where we left the matter in last month’s article.

Going forward, a question springs up naturally. What if some logic is required to render the template? What if you need conditional rendering that produces a different markup based on different run-time conditions? Some client-side code must be intertwined with markup to check the values of data items being bound as well as the state of other global JavaScript objects.

ASP.NET AJAX 4 defines a special set of namespaced attributes through which you attach custom behaviors to the HTML template. These behaviors are JavaScript expressions to be evaluated and executed at very specific stages of the rendering process. Figure 1 lists the predefined code attributes for conditional template rendering.

Code attributes are recognized by the template builder and their content is appropriately used in the rendering process. Attributes in Figure 1 can be attached to any DOM elements used in an ASP.NET AJAX template.

Note that attributes in Figure 1 were scoped in the code: namespace up until Preview 4 of the ASP.NET AJAX 4 library and Visual Studio 2010 beta 1. Starting with Preview 5, the ASP.NET team eliminated the code: namespace, and it is using only the sys: namespace for everything.

Figure 1 Attributes for Conditional Rendering of DOM Elements Within a Template

Conditional Rendering in Action

The sys:if attribute is assigned a Boolean expression. If the expression returns true, then the element is rendered; otherwise, the algorithm proceeds with the next step. Here’s a trivial example, just to quickly illustrate the point:

<div sys:if="false">
:
</div>

While processing this markup, the builder just ignores the DIV tag and all of its content. In a certain way, the sys:if attribute can be used to comment out parts of the template at development time. All in all, assigning a constant value of false to the sys:if attribute is not much different from doing the following in C#:

if (false)
{
  :
}

Setting sys:if to a false value, doesn’t exactly hide a HTML element. It should be noted that any ASP.NET AJAX template is initially treated like plain HTML by the browser. This means that any template is fully processed to a document object model (DOM) tree. However, as an ASP.NET AJAX template is decorated with the sys-template attribute, nothing of the DOM tree shows up when the page is displayed. In fact, the sys-template attribute is a CSS class that contains the following:

.sys-template { display:none; visibility:hidden; }

The sys:if attribute keeps the HTML element off the actual markup for the template. The sys:if attribute is ignored if attached to a HTML element outside any ASP.NET AJAX templates. The sys:if attribute is not currently associated with an else branch.

If defined, the sys:codebefore and sys:codeafter attributes execute before and after the rendering of the HTML element. The two attributes can be used together or individually as it best suits you. The content of the attributes must be a piece of executable JavaScript code.

Altogether, the code attributes give you enough power to deal with nearly every possible scenario, even though not always with a straightforward solution. Let’s consider a less trivial example of sys:if and sys:codebefore.

By the way, you may have noticed a few weird $-prefixed variables in the preceding code. Let me briefly introduce them just before introducing the example.

Template Pseudo-Variables

In the custom code you use within the code attributes of a template, you have access to the full set of public properties of the data item. In addition, you can access some extra variables defined and exposed by the template builder for your convenience.

Currently, the documentation refers to them as “pseudo-columns” but I personally like the term “pseudo-variable.” Figure 2 lists them all.

These pseudo-variables provide a glimpse of the internal state of the rendering engine as it is working. You can use any of such variables as you would use any JavaScript variable in the ASP.NET AJAX template.

Figure 2 Pseudo-Variables Supported by the ASP.NET AJAX Template Builder

Coloring Matching Rows in a Table

As an example, let’s consider a page that shows a list of customers plus a drop-down list to pick up a country/region. Whenever a new country/region is selected, the list of customers refreshes to render customers from that country/region in a different style (see Figure 3).


Figure 3 A Sample ASP.NET AJAX Page with Conditional Template Rendering

Figure 4 shows the markup for the sample page. As you can see, the page is a content page associated with a master page. The required Sys namespace is declared in the Body tag defined in the master page.

Figure 4 Code Attributes in Action

<asp:Content ContentPlaceHolderID="PH_Body" runat="server">
<asp:ScriptManagerProxy runat="server" ID="ScriptManagerProxy1">
<Scripts>
<asp:ScriptReference Name="MicrosoftAjax.js"
Path="~/MicrosoftAjax.js" />
<asp:ScriptReference Path="~/MicrosoftAjaxTemplates.js" />
</Scripts>
</asp:ScriptManagerProxy>
<div>
<asp:DropDownList ID="listOfCountries" runat="server"
ClientIDMode="Static"
onchange="listOfCountries_onchange()">
</asp:DropDownList>
<table id="gridLayout">
<tr>
<th>ID</th>
<th>COMPANY</th>
<th>COUNTRY</th>
</tr>
<tbody id="grid" class="sys-template">
<tr sys:if="$dataItem.Country != currentCountry">
<td align="left">{{ ID }}</td>
<td align="right">{{ CompanyName }}</td>
<td align="right">{{ Country }}</td>
</tr>
<tr sys:if="$dataItem.Country == currentCountry"
class="highlight">
<td align="left"
sys:codebefore="if($dataItem.Country == 'USA') {
$element.style.color = 'orange';
$element.style.fontWeight=700;
}">
{{ ID }}
</td>
<td align="right">{{ CompanyName }}</td>
<td align="right">{{ Country }}</td>
</tr>
</tbody>
</table>
</div>
</asp:Content>

You should note that Preview 5 of ASP.NET AJAX requires you to override the MicrosoftAjax.js file that comes with the ScriptManager control and beta 1. This is a temporary fix that will no longer be necessary as assemblies are updated to beta 2 and then to release to manufacturing.

Before coming to grips with ASP.NET AJAX templates, let me focus on the markup code for the drop-down list control.

Setting up the Drop-Down List

The code for the drop-down list for countries/regions is as follows:

<asp:DropDownList ID="listOfCountries" runat="server" 
     ClientIDMode="Static" 
     onchange="listOfCountries_onchange()">
</asp:DropDownList>

As you can see, the control assigns a value to the new ClientIDMode property and provides a client-side handler for the DOM-level onchange event. The control is bound to its data on the server, precisely in the classic Page_Load method:

protected void Page_Load(object sender, EventArgs e)
{
   if (!IsPostBack)
   {
      // Fetch data
      string[] countries = new string [] {"[All]", "USA", ... "};
      // Serialize data to a string
      string countriesAsString = "’[All]’, ‘USA’, ...’";
      // Emit the array in the page
      this.ClientScript.RegisterArrayDeclaration(
           "theCountries", countriesAsString);
      // Bind data to the drop-down list
      listOfCountries.DataSource = countries;
      listOfCountries.DataBind();
   }
}

The binding procedure is articulated in two steps. First, a JavaScript array is emitted in the response that contains the same data bound programmatically to the DropDownList control. Next, the classic server-side data binding takes place.

This technique is known as Dual-Side Templating and is a variation of the standard client-side data binding pattern. The difference consists in the fact that data to bind is fetched on the server the first time the page is accessed and served to the client as an embedded JavaScript array.

Further client-side data binding that proves necessary can then take place using the embedded array. In this way, you basically save an extra roundtrip to get the data. This variation of the classic client data binding is helpful when you display static data that don’t change during the user interaction. In the example, I used this technique only for getting the list of countries/regions; the list of customers, instead, is fetched from the Web server using a Web service.

When you use a server-side control to emit HTML markup, you may have little control over the actual ID if you’re using a master page. In ASP.NET AJAX 4, the new ClientIDMode property gives you more flexible ways to deal with the issue.

In particular, if you set ClientIDMode to Static as in the example, then the client ID of the HTML element will match exactly the server ID. This trick is not useful when you’re going to repeat that server control in the context of a data-bound templated control.

The following script code handles the change of selection event in the drop-down list:

<script language="javascript" type="text/javascript">
    var currentCountry = "";
    function listOfCountries_onchange() {
        // Pick up the currently selected item
        var dd = $get("listOfCountries");
        currentCountry = dd.options[dd.selectedIndex].value;
        
        // Refresh the template
        refreshTemplate();
    }
    function refreshTemplate() {
        var theDataView = $find("grid");
        theDataView.refresh();
    }
</script>

Note that this code will raise a JavaScript error if you don’t set the ClientIDMode property of the DropDownList control to Static. This is because of the ID-mangling work that ASP.NET usually does to ensure that when master pages are used each produced HTML element has a unique ID.

The preceding onchange event handler saves the name of the currently selected country/region to a global JavaScript variable and then refreshes the ASP.NET AJAX template. Let’s focus on templates now.

Conditional Templates

The template is created and populated programmatically as below:

<script language="javascript" type="text/javascript">
   function pageLoad()
   {
      dv = $create(Sys.UI.DataView,
              {
                 autoFetch: true,
                 dataProvider: “/ajax40/mydataservice.asmx",
                 fetchOperation: “LookupAllCustomers"
              },
              {},
              {},
              $get(“grid")
       );
    }
</script>

The DataView client control makes a call to the specified Web service, performs the given fetch operation and uses any returned data to fill the ASP.NET AJAX template rooted in the DOM element named “grid.”

The overall template is rendered using a DataView instance bound to the return value of the LookupAllCustomers method on the sample Web service. The method returns a collection of Customer objects with properties such as ID, CompanyName and Country.

The template will stay bound to its data for the entire lifetime of the page regardless of the changes that may occur to the data. What if, instead, you just want to modify the rendering of the template—no data refresh whatsoever—as certain run-time conditions change? To get this, you need to insert code attributes in the template.

What you really need here is a truly conditional rendering that renders the template in one way if a given condition is verified and otherwise if the condition is not verified. As mentioned, the sys: if attribute doesn’t support “if-then-else” semantics, and all it does is rendering, or ignoring, its parent element based on the value of the Boolean guard.

A possible workaround to simulate the two branches of a condition consists in using two mutually exclusive portions of template, each controlled by a different Boolean expression. Also shown in Figure 4, the code follows the schema below:

<tr sys:if="$dataItem.Country != currentCountry">
  :
</tr>
<tr sys:if="$dataItem.Country == currentCountry" 
    class="highlight">
  :
</tr>

The variable currentCountry is a global JavaScript variable that contains the name of the currently selected country/region. The variable is updated every time the onchange event is raised by the HTML markup for the server-side DropDownList control.

In the preceding code snippet, the former TR element is rendered conditionally based on the value of the Country property of the data item being bound. If the variable matches the selected country/region, the former TR is skipped. This behavior relies on the fact that the global variable is initialized to the empty string and doesn’t subsequently match any value. As a result, the table row template is initially rendered for any returned customer.  

As the user makes a selection in the drop-down list, the global currentCountry variable is updated. However, this action doesn’t automatically trigger any refresh on the template as you see in Figure 3. The refresh of the template must be explicitly commanded in the onchange event handler. Here’s a possible way of doing that:

var theDataView = $find("grid");
theDataView.refresh();

The $find function is shorthand for a lookup function that in the Microsoft AJAX library retrieves instances of components. To use $find (or $get), you must have a ScriptManager control at work and configured in a way that references the MicrosoftAjax.js core library.  Once you have retrieved the DataView instance associated with the “grid” template, you just invoke its refresh method. Internally, the method just recompiles the template and updates the DOM. Note that you don’t strictly need to retrieve the DataView instance from the list of registered components. You can also save the DataView instance to a global variable as you create it upon page loading:

var theDataView = null;
function pageLoad()
{
   theDataView = $create(Sys.UI.DataView, ...); 
   :}

Next, in the onchange handler you just call the refresh method on the global instance:

theDataView.refresh();

In this first example, I used a drop-down list to render the portion of the user interface responsible for triggering changes on the rest of page. The drop-down list element is particular because it incorporates the logic to raise a change event when one of its elements is selected.

ASP.NET AJAX, however, provides a more general mechanism to trigger change/notification events that result in page-specific operations. Let’s rework the previous example using a plain hand-made list instead of a drop-down list.

The sys:command Attribute

The list of countries/regions is now generated using HTML tags for an unordered bulleted list. The template is as follows:

<fieldset>
   <legend><b>Countries</b></legend>
   <ul id="listOfCountries" class="sys-template">
       <li>
           {{ $dataItem }}
       </li>
   </ul>
</fieldset>

The template is programmatically attached to a DataView control for rendering purposes. Data to fill up the template is provided via an embedded JavaScript array. The JavaScript array that contains the list of countries is emitted from the server using the services of the ClientScript object on the Page class. Unlike the previous example, the Page_Load code doesn’t include server-side binding operations:

protected void Page_Load(object sender, EventArgs e)
{
   if (!IsPostBack)
   {
      string[] countries = new string [] {"[All]", "USA", ... "};
      string countriesAsString = "’[All]’, ‘USA’, ...’";
      this.ClientScript.RegisterArrayDeclaration(
           "theCountries", countriesAsString);
   }
}

A second DataView control is instantiated on the client as the page is loaded. Here’s the modified code for the JavaScript pageLoad function, as shown in Figure 5.

Figure 5 JavaScript pageLoad Function

<script language="javascript" type="text/javascript">
    function pageLoad()
    {
        $create(Sys.UI.DataView,
            {
                autoFetch: true,
                dataProvider: "/ajax40/mydataservice.asmx",
                fetchOperation: "LookupAllCustomers"
            },
            {},
            {},
            $get("grid")
        );
        $create(Sys.UI.DataView,
            {
                autoFetch: true,
                initialSelectedIndex: 0,
                selectedItemClass:"selectedItem",
                onCommand: refreshTemplate,
                data:theCountries
            },
            {},
            {},
            $get("listOfCountries")
        );
    }
</script>

As you can see, the second DataView used to bind countries/regions to a UL-based template has quite a different structure than the other.

The first difference is that the data property is used to import data. This is the correct procedure when embedded data is being used.

When the data source is an array of user-defined objects, you perform binding via the {{expression}} syntax. The content of the expression is typically the name of a public property exposed by the data item. In this example, instead, the source of data binding is a plain array of strings. Subsequently, the data item is a string with no public properties to refer to in the binding expression. In this case, you resort to the following:

<ul>
  <li>{{ $dataItem }}</li>
</ul>

The initialSelectedIndex and selectedItemClass properties configure the expected behavior of the DataView as far the selection of a displayed item is concerned.

The DataView can attach the template the built-in behavior of selection. The item at the position specified by initialSelectedIndex will be styled according to the CSS class set via the selectedItemClass property. You set initialSelectedIndex to -1 if you don’t want any selection made on first display.

The list that results from the template is a plain UL list and, as such, it doesn’t natively incorporate any logic to handle selection, as you see here:

<ul>
  <li>[All]</li>
  <li>USA</li> 
  :
</ul>

By using the sys:command attribute on a HTML element within a template, you instruct the template builder to dynamically attach a bunch of event handlers to the element, as follows:

<ul id="listOfCountries" class="sys-template">
    <li sys:command="select">
        {{ $dataItem }}
    </li>
</ul>

Figure 6 shows how such modified LI elements show up in the Developer Tools window of Internet Explorer 8. The sys:command attribute takes a string value that represent the name of the command triggered. The name is actually up to you. Commands are triggered by clicking on the element. Common commands are select, delete, insert and update. When the command is triggered, the DataView fires the onCommand event. Here’s the code that handles the onCommand event and refreshes the template:

<script type="text/javascript">
    var currentCountry = "";
    function refreshTemplate(args) 
    {
      if (args.get_commandName() == "select") 
      {
        // Pick up the currently selected item
        currentCountry = args.get_commandSource().innerHTML.trim();
                
        // Refresh
        var theDataView = $find("grid");
        theDataView.refresh();
      }
    }
</script>


Figure 6 Event Handlers Dynamically Added as the Effect of sys:command

The same approach can be used for a drop-down list as long as you emit it directly in HTML, as below:

<select>
  <option sys:command="select"> {{ $dataItem }} </option>
</select>

Note, though, that a bug prevents the preceding code to work as expected in beta 1. (Figure 7 shows the sample page in action.)


Figure 7 Commands are Used to Handle Selection

HTML Attributes

In ASP.NET AJAX 4, a special bunch of sys: attributes specify ad hoc bindings for HTML attributes. Functionally speaking, these attributes are like HTML attributes and you won’t notice any different behavior. Figure 8 lists the namespaced HTML attributes.

All element attributes in a template can be prefixed with the sys: namespace. So what’s the ultimate reason for using mapped attributes? And why are only a few of them are listed in Figure 8?

Figure 8 HTML Mapped Attributes

Often you want to bind to HTML attributes but you don’t want to set the attribute itself to your {{...}} binding expression. From the DOM perspective, the binding expression is simply a value assigned to an attribute and it is processed as such. This, of course, may have some unpleasant side effects. For example, if you bind to the value attribute of an input element, or to the content of an element, the binding string may appear for a second to the user as the page is being loaded. The same happens if you are using binding expressions outside of a template (i.e., live binding or two-way binding). In addition, there are a bunch of HTML attributes—those listed in Figure 8—where the use of binding expressions may originate unwanted effects. For example, consider the following markup:

<img src="{{ URL }}" />

It triggers a request for the string “URL” rather than the value of the property URL on the data item.

Other issues you may face include XHTML validation issues and in general wrong attribute resolution by the browser. If you prefix such critical attributes  with the sys namespace, you solve the issue.

So the best practice is to always prefix with the sys namespace any attributes being assigned a binding expression. The DOM doesn’t care about namespaced attributes, so attributes retain their binding expression with no side effects until it is processed by the template builder.

Namespaced attributes are recommended in client-side rendering, even though they are not certainly mandatory except in situations where they can save you the effects of wrong HTML parsing.

Whole New World

Templates and data binding open up a whole new world of possibilities to ASP.NET AJAX developers. Next month, I’ll be back to cover various types of binding, including live binding and master/detail views.   


Dino Esposito is an architect at IDesign and the co-author of “Microsoft .NET: Architecting Applications for the Enterprise” (Microsoft Press, 2008). Based in Italy, Dino is a frequent speaker at industry events worldwide.